use crate::http::{HttpBody, HttpContentType, HttpVersion};
use super::{HttpResponse, HttpResponseHeaders, HttpResponseLine};

#[derive(Debug, thiserror::Error)]
pub enum HttpResponseBuildError {
    #[error("No version given")]
    NoVersion,
    #[error("No status code given")]
    NoStatusCode,
}

#[derive(Debug, Default)]
pub struct HttpResponseBuilder {
    status: Option<u16>,
    reason: Option<String>,
    version: HttpVersion,
    headers: HttpResponseHeaders,
    body: Option<HttpBody>,
}

impl HttpResponseBuilder {
    pub fn status(mut self, status: u16) -> Self {
        self.status = Some(status);
        self
    }

    pub fn reason<S: Into<String>>(mut self, reason: S) -> Self {
        self.reason = Some(reason.into());
        self
    }

    pub fn version(mut self, version: HttpVersion) -> Self {
        self.version = version;
        self
    }

    pub fn header<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
        self.headers.other.insert(key.into(), value.into());
        self
    }

    pub fn content_type(mut self, ct: HttpContentType) -> Self {
        self.headers.content_type = Some(ct);
        self
    }

    pub fn content_length(mut self, len: u64) -> Self {
        self.headers.content_length = Some(len);
        self
    }

    pub fn content_encoding<S: Into<String>>(mut self, enc: S) -> Self {
        self.headers.content_encoding = Some(enc.into());
        self
    }

    pub fn connection<S: Into<String>>(mut self, conn: S) -> Self {
        self.headers.connection = Some(conn.into());
        self
    }

    pub fn server<S: Into<String>>(mut self, s: S) -> Self {
        self.headers.server = Some(s.into());
        self
    }

    pub fn set_cookie<S: Into<String>>(mut self, cookie: S) -> Self {
        self.headers.set_cookie.push(cookie.into());
        self
    }

    pub fn cache_control<S: Into<String>>(mut self, val: S) -> Self {
        self.headers.cache_control = Some(val.into());
        self
    }

    pub fn date<S: Into<String>>(mut self, d: S) -> Self {
        self.headers.date = Some(d.into());
        self
    }

    pub fn location<S: Into<String>>(mut self, loc: S) -> Self {
        self.headers.location = Some(loc.into());
        self
    }

    pub fn body_text<S: Into<String>>(self, text: S) -> Self {
        self.body(HttpBody::from_bytes(text.into()))
    }

    pub fn body_json(self, json: serde_json::Value) -> Self {
        self.body(HttpBody::from_bytes(json.to_string()))
    }

    pub fn body_bytes<B: Into<Vec<u8>>>(self, bytes: B) -> Self {
        self.body(HttpBody::from_bytes(bytes))
    } 

    pub fn upgrade<S: Into<String>>(mut self, upgrade: S) -> Self {
        self.headers.upgrade = Some(upgrade.into());
        self
    }

    pub fn sec_websocket_accept<S: Into<String>>(mut self, key: S) -> Self {
        self.headers.sec_websocket_accept = Some(key.into());
        self
    }

    pub fn body(mut self, body: HttpBody) -> Self {
        self.headers.content_length = Some(body.content_length());
        self.body = Some(body);
        self
    }
}

impl HttpResponseBuilder {
    pub fn build(self) -> Result<HttpResponse, HttpResponseBuildError> {
        let status_code = self.status.ok_or(HttpResponseBuildError::NoStatusCode)?;
        let reason_phrase = self.reason.unwrap_or_else(|| HttpResponse::default_reason(status_code).to_string());
        Ok(HttpResponse {
            line: HttpResponseLine {
                status_code,
                reason_phrase,
                version: self.version,
            },
            headers: self.headers,
            body: self.body,
        })
    }
}
